package be.rivendale.mathematics;

import org.apache.commons.lang.Validate;

/**
 * Represents a <a href="http://en.wikipedia.org/wiki/Half_line#Ray">ray or halfline</a>,
 * which is a line that starts in a point called the origin, and runs to infinity in one direction of this origin point.
 * This means that defining a ray can be done by:
 * <ul>
 * <li>an origin point and a second point though which the ray runs</li>
 * <li>an origin point and a direction vector to which the ray runs</li>
 * </ul>
 */
public class Ray {
    /**
     * The line on which this ray lies. This ray is in fact a subset of the points that lie on the line in such a way
     * that the ray only includes the line on one side of one of the points that define the line.
     */
    private Line line;

    /**
     * Constructs a ray starting at the origin point specified, and passing through the other point.
     * @param origin the point at which this ray starts.
     * @param passThroughPoint the point through witch the ray passes.
     */
    public Ray(Point origin, Point passThroughPoint) {
        Validate.notNull(origin, "The origin of a ray must not be null");
        Validate.notNull(passThroughPoint, "The secondary point of a ray must not be null");
        this.line = new Line(origin, passThroughPoint);
    }

    /**
     * Returns the origin of the ray.
     * This is the point that limits the ray in one direction.
     * @return The origin of the ray.
     */
    public Point getOrigin() {
        return line.getA();
    }

    /**
     * Returns the secondary point of this ray.
     * The secondary point is a point where the ray passes through, but does not end.
     * @return The secondary point of this ray.
     * @see #getOrigin()
     */
    public Point getPassThroughPoint() {
        return line.getB();
    }

    /**
     * Returns the direction vector of this ray.
     * The direction vector is the vector between the origin point and the second point.
     * This vector will always point to the direction where the ray passes into infinity.
     * @return An unnormalized vector between the two ray points.
     */
    public Vector direction() {
        return line.direction();
    }

    /**
     * Calculates a new point that lies somewhere on this ray.
     * <p>This is done by using a ray direction multiplier. This factor is usually referred to as 't' in the
     * parametric equation of the ray: <code>(x, y, z) = (x0, y0, z0) + [<strong>t</strong> * (a, b, c)] </code>.
     * The result is a point that lies n times the distance between the {@link #getOrigin() origin} and the
     * {@link #getPassThroughPoint() secondary point}, starting from the origin</p>
     * <p>As a result, passing a multiplier of <code>1</code> will result in a point that is equal to the
     * {@link #getPassThroughPoint () secondary point} (because it lies at 1 time the distance between the
     * {@link #getOrigin() origin} and the {@link #getPassThroughPoint () secondary point}, which is obviously the same as
     * the secondary point itself)</p>
     * <p>Also, when passing a multiplier of <code>0</code>, you will end up with a point that is equal to the
     * {@link #getOrigin() origin} (since 0 times the distance starting from the origin is the origin itself</p>
     * @param t The multiplication factor of the ray's direction to calculate a new point. This value must not be
     * negative (since a ray does not have points that lie before the origin).
     * @return A point that lies on the same ray.
     * The point lie at a position exaclty the multiplier times the distance between the origin and the secondary point.
     * More specifically:
     * <ul><li>Passing a value between 0..1 will result in a point that lies between the origin and the secondary
     * point.</li><li> When passing a value larger then 1, it will result in a point that
     * lies further away from the origin then the secondary point.</li></ul>
     * @see Line#pointOnLine(double)
     */
    public Point pointOnRay(double t) {
        Validate.isTrue(t >= 0, "A point on a ray can only be found when passing positive values of the ray's equation parameter 't'");
        return line.pointOnLine(t);
    }
}
